package com.sugon.aop;

import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.sugon.annotation.LimitSubmit;
import com.sugon.config.RedisUtil;
import com.sugon.utils.RRException;
import com.sugon.utils.ShiroUtils;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;
import java.util.Arrays;

@Component
@Aspect
@Slf4j
public class LimitSubmitAspect {
    //封装了redis操作各种方法
    @Autowired
    private RedisUtil redisUtil;

    @Pointcut("@annotation(com.sugon.annotation.LimitSubmit)")
    private void pointcut() {
    }

    @Around("pointcut()")
    public Object handleSubmit(ProceedingJoinPoint joinPoint) throws Throwable {
        // 当前登录用户名
        String userName = ShiroUtils.getUserName();

        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        //获取注解信息
        LimitSubmit limitSubmit = method.getAnnotation(LimitSubmit.class);
        // 等待时长
        int submitTimeLimiter = limitSubmit.limit();
        String redisKey = limitSubmit.key();
        boolean needAllWait = limitSubmit.needAllWait();
        String key = getRedisKey(userName, joinPoint, redisKey);
        Object result = redisUtil.get(key);
        if (result != null) {
            throw new RRException(limitSubmit.message());
        }
        redisUtil.set(key, userName, submitTimeLimiter);
        try {
            Object proceed = joinPoint.proceed();
            return proceed;
        } catch (Throwable e) {
            log.error("Exception in {}.{}() with cause = \'{}\' and exception = \'{}\'", joinPoint.getSignature().getDeclaringTypeName(),
                    joinPoint.getSignature().getName(), e.getCause() != null ? e.getCause() : "NULL", e.getMessage(), e);
            throw e;
        } finally {
            if (!needAllWait) {
                redisUtil.del(redisKey);
            }
        }
    }

    /**
     * 获取指定切点中特定用户的缓存KEY
     *
     * @param userName
     * @param joinPoint
     * @param key
     * @return
     */
    private String getRedisKey(String userName, ProceedingJoinPoint joinPoint, String key) {
        if (key.contains("%s")) {
            key = String.format(key, userName);
        }
        Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
        // 获取方法参数
        Object[] args = joinPoint.getArgs();
        LocalVariableTableParameterNameDiscoverer discoverer = new LocalVariableTableParameterNameDiscoverer();
        String[] parameterNames = discoverer.getParameterNames(method);
        if (parameterNames != null) {
            for (int i = 0; i < parameterNames.length; i++) {
                String item = parameterNames[i];
                String paramPrefix = "#" + item;
                // examineApproveLossRecord:%s:#eaParam.lossRecordId:
                if (key.contains(paramPrefix)) {
                    // 获取参数
                    String[] params = subParam(key, paramPrefix);
                    if (ArrayUtil.isNotEmpty(params)) {
                        for (String paramName : params) {
                            key = StrUtil.replace(key, paramPrefix + "." + paramName, ReflectUtil.getFieldValue(args[i], paramName).toString());
                        }
                    } else {
                        key = key.replace("#" + item, args[i].toString());
                    }
                }
            }
        }
        return key;
    }

    /**
     * 从防重复登录中截取参数
     * examineApproveLossRecord:%s:#eaParam.lossRecordId:#eaParam.result
     *
     * @param limitSubmitKey 防重复提交KEY
     * @param paramPrefix    参数前缀
     * @return
     */
    public static String[] subParam(String limitSubmitKey, String paramPrefix) {
        String separateLimitSubmitKey = limitSubmitKey + ":";
        return StrUtil.subBetweenAll(separateLimitSubmitKey, paramPrefix + ".", ":");
    }

}
